/* ----------------------------------NODE_OPTS------------------------------ */
const ATTR_NODE = 1 // 元素节点
const TEXT_NODE = 3 // 文本节点
const COMMENT_NODE = 8 // 注释节点

function getCompute(el) {
  if (el instanceof HTMLElement) {
    return window.getComputedStyle(el)
  }
  return null
}

function isCommentNode(ele) {
  return ele.nodeType === COMMENT_NODE
}

function textNode(ele) {
  if (ele.nodeType === 3) {
    return ele.textContent
    //replace(/\s|\n|\r/g, '')
  }
  return ''
}

function nodeType(ele) {
  return ele.nodeType
}

function nodeParent(ele) {
  return ele.parentNode
}

function nodePrev(ele) {
  //上个兄弟节点
  return ele.previousSibling
}

function nodeNext(ele) {
  //下个兄弟节点
  return ele.nextSibling
}

function nodeName(ele) {
  if (isTextNode(ele)) return 'text'
  if (isCommentNode(ele)) return 'comment'
  return ele.nodeName.toLocaleLowerCase()
}

function nodeText(ele) {
  return ele.textContent.trim()
}

function isWhiteTextNode(ele) {
  if (ele.nodeType === TEXT_NODE) {
    return ele.textContent.replace(/\s+/g, '') === ''
  }
  return false
}

function isTextNode(ele) {
  return ele.nodeType === TEXT_NODE
}

function createElement(tagName) {
  return document.createElement(tagName)
}

function replaceElement(node, newNode) {
  if (!node) return
  let par = nodeParent(node)
  par.replaceChild(newNode, node)
}

function firstChild(node) {
  if (!node) return
  return node.firstChild
}

function getRect(ele) {
  if (!ele) return
  return {
    top: ele.offsetTop,
    left: ele.offsetLeft,
    width: ele.offsetWidth, //parseFloat(comp.width),
    height: ele.offsetHeight, //parseFloat(comp.height)//ele.offsetHeight
  }
}
const clientTop = document.documentElement.clientTop
const clientLeft = document.documentElement.clientLeft

function getBoundingClientRect(ele) {
  if (!ele) return
  let react = ele.getBoundingClientRect()
  return {
    top: react.top,
    left: react.left,
    width: react.width,
    height: react.height,
  }
}
/* ----------------------------------NODE_OPTS------------------------------ */
function isNumber(num) {
  return !Number.isNaN(parseInt(num))
}
function toNumber(num) {
  return parseFloat(num)
}

function findMaxLengthNum(arr) {
  let min = (max = arr[0] || 0)
  for (var i = 0; i < arr.length; i++) {
    if (min > arr[i]) {
      min = arr[i]
    }
    if (max < arr[i]) {
      max = arr[i]
    }
  }
  return { maxAbs: Math.max(Math.abs(min), max),min }
}

// 浏览器相关属性值

// 布局相关
const RELATIVE = 'relative'
const STATIC = 'static'
const ABSOLUTE = 'absolute'
const FIXED = 'fixed'

const PADDING_TOP = 'padding-top'
const PADDING_RIGHT = 'padding-right'
const PADDING_BOTTOM = 'padding-bottom'
const PADDING_LEFT = 'padding-left'

function textStyle(style) {
  return {
    color: style.color,
    fontSize: style.fontSize,
    fontStyle: style.fontStyle,
    fontWeight: style.fontWeight,
    fontFamily: style.fontFamily,
    lineHeight: style.lineHeight,
  }
}

function reactStyle(style) {
  return {
    color: style.color,
    backgroundColor: style.backgroundColor,
    backgroundImage: style.backgroundImage,
    borderWidth: style.borderWidth,
    borderStyle: style.borderStyle,
    borderColor: style.borderColor,
    borderRadius: style.borderRadius,
    boxShadow: style.boxShadow,
    boxShadow: style.boxShadow,
    transform: style.transform,
    transformOrigin: style.transformOrigin,
  }
}

class LayoutTree {
  constructor(node) {
    if (!node) {
      console.warn('必须输入渲染节点')
      return
    }
    this.hash = 0
    this.maxDeep = 0
    this.maxZIndexAbs = 0
    this.minZIndex = 0

    // this.cacheTreeMap = {}
    this.renderList = []
    this.node = node
    let react = getBoundingClientRect(node)
    this.layoutWidth = react.width
    this.layoutHeight = react.heigh
    this.layoutTop = react.top
    this.layoutLeft = react.left

    this.normalizeTextNode(node, {})

    this.traverse(node, {})
    return this.computeLevelTree() // 计算元素堆叠层级，返回渲染列表
  }

  computeLevelTree() {
    // 找出最深的树
    let maxDeep = this.maxDeep,
      minNum = this.minZIndex
    /* 
    数据补位后排序
    */
    // 具有最多位数的值
    let maxStrLen = (this.maxZIndexAbs + '').length + 1
    for (let j = 0; j < this.renderList.length; j++) {
      let zIndex = this.renderList[j].zIndex.slice(0)
      let addZero = maxDeep - zIndex.length
      if (addZero > 0) {
        for (let i = 0; i < addZero; i++) {
          zIndex.push(0)
        }
      }

      let zIndexStr = ''
      for (let k = 0; k < zIndex.length; k++) {
        let toStr = minNum < 0 ? zIndex[k] + (minNum * -1) + '' : zIndex[k] + ''
        let diff = maxStrLen - toStr.length
        if (diff > 0) {
          for (let i = 0; i < diff; i++) {
            toStr = '0' + toStr
          }
        }
        zIndexStr += toStr
      }
      this.renderList[j].zIndexNum = zIndexStr
    }
    let isSafeNum = true,
      section = 0
    if (this.renderList[0] && this.renderList[0].zIndexNum && this.renderList[0].zIndexNum.length > 40) {
        isSafeNum = false
        section = Math.ceil(this.renderList[0].zIndexNum.length / 10)
    }
    if (isSafeNum) {
      return this.renderList.sort(function (a, b) {
        return +a.zIndexNum - +b.zIndexNum
      })
    } else { // 超过安全数后采用分段比较
      return this.renderList.sort(function (a, b) {
        let compare = 0
        for (let i = 0; i < section; i++) {
          compare = +a.zIndexNum.slice(i * 10, i * 10 + 10) - +b.zIndexNum.slice(i * 10, i * 10 + 10)
          if(compare !== 0) {
            break
          }
        }
        return compare
      })
    }
  }

  fixPos(node, style) { // 子节点需要减去根节点坐标偏移
    let react = getBoundingClientRect(node)
    return {
      top: react.top - this.layoutTop,
      left: react.left - this.layoutLeft,
      width: react.width,
      height: react.height,
    }
  }

  normalizeTextNode(node, vnode) {
    if (!node) return
    if (isTextNode(node) && !isWhiteTextNode(node)) {
      const prevNode = nodePrev(node)
      const nextNode = nodeNext(node)
      if (prevNode || nextNode) {
        //有兄弟节点
        const whiteTag = createElement('span')
        whiteTag.textContent = nodeText(node)
        whiteTag.setAttribute('ignore', true)
        replaceElement(node, whiteTag)
      }
    } else {
      // hack 解决按钮计算不准
      switch (nodeName(node)) {
        case 'button':
          const whiteTag = createElement('span')
          whiteTag.textContent = nodeText(node)
          if (firstChild(node)) {
            replaceElement(firstChild(node), whiteTag)
          }
          break
      }
      vnode.children = []
    }

    if (node.childNodes && node.childNodes.length > 0) {
      for (let i = 0; i < node.childNodes.length; i++) {
        const item = node.childNodes[i]
        if (isTextNode(item) && isWhiteTextNode(item)) {
          continue
        }
        vnode.children.push({})
        this.normalizeTextNode(item, vnode.children[vnode.children.length - 1])
      }
    }
  }

  computeTextRect(node, vnode) {
    let parNode = nodeParent(node)

    const parNodeStyle = getCompute(parNode)

    const parentNodeRect = this.fixPos(parNode, parNodeStyle)

    let pTop = parseFloat(parNodeStyle[PADDING_TOP] || 0)
    let pLeft = parseFloat(parNodeStyle[PADDING_LEFT] || 0)
    let pRight = parseFloat(parNodeStyle[PADDING_RIGHT] || 0)

    vnode.react.top = parentNodeRect.top + pTop
    vnode.react.left = parentNodeRect.left + pLeft
    vnode.react.width = parentNodeRect.width - pLeft - pRight

    vnode.text = nodeText(node)
    vnode.style = textStyle(parNodeStyle)
    this.computeText3D(node, vnode, parNodeStyle) // 3d变换计算
  }

  computeEleRect(node, vnode) {
    vnode.children = []
    vnode.type = 'div'
    const style = getCompute(node)
    vnode.style = reactStyle(style)
    vnode.react = this.fixPos(node, style)
    switch (vnode.tagName) {
      case 'img':
        vnode.type = 'img'
        vnode.src = node.src
        break
      case 'video':
        vnode.type = 'video'
        break
      case 'tree':
        {
          let attrs = node.attributes
          let temp = {}
          for (let a = 0; a < attrs.length; a++) {
            const attr = attrs[a]
            temp[attr.nodeName] = attr.textContent
          }
          vnode.attrs = temp
          vnode.type = 'tree'
        }
        break
      case 'star':
        {
          let attrs = node.attributes
          let temp = {}
          for (let a = 0; a < attrs.length; a++) {
            const attr = attrs[a]
            temp[attr.nodeName] = attr.textContent
          }
          vnode.attrs = temp
          vnode.type = 'star'
        }
        break
      case 'span':
        if (node.attributes.ignore) {
          vnode.ignore = true
        }
    }
    this.computeReact3D(node, vnode, style) // 3d变换计算
  }

  computeZIndex(node, vnode) {
    if (!vnode.zIndex) {
      const style = getCompute(node)
      if (!style) {
        vnode.zIndex = [0]
        return
      }
      let zIndex = style.zIndex
      if (isNumber(zIndex)) {
        vnode.zIndex = [toNumber(zIndex)]
      } else {
        vnode.zIndex = [0]
      }
      this.getTreeDeepAndZIndex(vnode.zIndex[0], vnode.zIndex.length)
    }
  }

  getTreeDeepAndZIndex(zIndex, deep) {
    let zIndexAbs = Math.abs(zIndex)
    if (zIndexAbs > this.maxZIndexAbs) {
      this.maxZIndexAbs = zIndexAbs
    }
    if (this.minZIndex > zIndex) {
      this.minZIndex = zIndex
    }
    if (deep > this.maxDeep) {
      this.maxDeep = deep
    }
  }

  computeChildZIndex(node, vnode) {
    const style = getCompute(node)
    let par = vnode.parent.zIndex.slice(0)

    if (!style || !style.position) {
      par.push(0)
      vnode.zIndex = par
      let vnodeZLen = vnode.zIndex.length
      this.getTreeDeepAndZIndex(vnode.zIndex[vnodeZLen - 1], vnodeZLen)
      return
    }

    let position = style.position
    let transform = style.transform !== 'none'
    let marginTop = style.marginTop
    switch (position) {
      case RELATIVE:
      case ABSOLUTE:
      case FIXED:
        let zIndex = style.zIndex
        if (isNumber(zIndex)) {
          par.push(toNumber(zIndex))
        } else {
          par.push(0)
        }
        vnode.zIndex = par
        break
      case STATIC:
        if (transform) {
          par.push(1)
          vnode.zIndex = par
        } else {
          if (isNumber(marginTop) && toNumber(marginTop) < 0) {
            par.push(1)
            vnode.zIndex = par
          } else {
            par.push(0)
            vnode.zIndex = par
          }
        }
        break
    }
    let vnodeZLen = vnode.zIndex.length
    this.getTreeDeepAndZIndex(vnode.zIndex[vnodeZLen - 1], vnodeZLen)
  }

  computeReact3D(node, vnode, style) {
    if (vnode.style.transform !== 'none') {
      let react = getRect(node)
      vnode.react = {
        top: react.top - this.layoutTop,
        left: react.left - this.layoutLeft,
        width: react.width,
        height: react.height,
      }
    }
    if (
      vnode.parent &&
      vnode.parent.style.transform !== 'none' &&
      vnode.style.transform === 'none'
    ) {
      vnode.style.transform = vnode.parent.style.transform
      let react = getRect(node)
      vnode.react = {
        top: react.top - this.layoutTop,
        left: react.left - this.layoutLeft,
        width: react.width,
        height: react.height,
      }
    }
  }

  computeText3D(node, vnode, style) {
    if (vnode.parent.style.transform !== 'none') {
      vnode.style.transform = vnode.parent.style.transform
      vnode.react = vnode.parent.react
    }
  }

  filterNoNeedNode(vnode) {
    switch (vnode.tagName) {
      case 'comment':
        return false
    }
    return true
  }

  traverse(node, vnode) {
    if (!node) return
    vnode.nodeType = nodeType(node)
    vnode.tagName = nodeName(node)
    vnode.ele = node
    this.computeZIndex(node, vnode)
    this.hash++
    vnode.id = 'id' + this.hash
    if (isTextNode(node)) {
      if (!isWhiteTextNode(node)) {
        vnode.type = 'text'
        vnode.react = {
          top: 0,
          left: 0,
          width: 0,
          height: 0,
        }

        this.computeTextRect(node, vnode)
      }
    } else {
      if (!isCommentNode(node)) {
        this.computeEleRect(node, vnode)
      }
    }
    // this.cacheTreeMap[vnode.id] = vnode
    if (this.filterNoNeedNode(vnode)) {
      this.renderList.push(vnode)
    }
    if (node.childNodes && node.childNodes.length > 0) {
      for (let i = 0; i < node.childNodes.length; i++) {
        const item = node.childNodes[i]
        if (isTextNode(item) && isWhiteTextNode(item)) {
          continue
        }
        vnode.children.push({})
        let child = vnode.children[vnode.children.length - 1]
        if (child) {
          child.parent = vnode
          this.computeChildZIndex(item, child)
        }
        this.traverse(item, child)
      }
    }
    return vnode
  }
}
